package cn.juque.jdfsystem.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.NumberUtil;
import cn.juque.common.base.PageInfo;
import cn.juque.common.constants.MessageEnum;
import cn.juque.common.constants.NumberConstant;
import cn.juque.common.constants.StringConstants;
import cn.juque.common.constants.YesOrNoEnum;
import cn.juque.common.exception.AppException;
import cn.juque.common.utils.ValidatorUtil;
import cn.juque.jdfsystem.bo.FileInfoSaveBO;
import cn.juque.jdfsystem.cache.ConfigInfoCache;
import cn.juque.jdfsystem.cache.FileInfoCache;
import cn.juque.jdfsystem.cache.PlatformInfoCache;
import cn.juque.jdfsystem.cache.RegisterInfoCache;
import cn.juque.jdfsystem.domain.FileInfoDO;
import cn.juque.jdfsystem.entity.FileInfo;
import cn.juque.jdfsystem.entity.PlatformInfo;
import cn.juque.jdfsystem.enums.ConfigCodeEnum;
import cn.juque.jdfsystem.enums.FileInfoScopeEnum;
import cn.juque.jdfsystem.enums.JdfSystemMsgEnum;
import cn.juque.jdfsystem.enums.LogOperateTypeEnum;
import cn.juque.jdfsystem.helper.FileInfoHelper;
import cn.juque.jdfsystem.service.IFileInfoService;
import cn.juque.jdfsystem.service.ILogInfoService;
import cn.juque.jdfsystem.threads.DelFileThreadPool;
import cn.juque.lucenecandy.core.base.*;
import cn.juque.lucenecandy.core.enums.MatchTypeEnum;
import cn.juque.lucenecandy.helper.IndexHelper;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.SortField;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Set;

/**
 * @author JUQUE
 * @version 1.0.0
 * <ul>
 *  <li>simple-generator</li>
 *  <li>文件信息</li>
 * </ul>
 * @date 2023-01-09 10:55:50
 **/
@Service("fileInfoService")
public class FileInfoServiceImpl implements IFileInfoService {

    private static final Integer ZERO = 0;

    private static final BigDecimal UNIT_M = new BigDecimal("1048576");

    @Resource
    private ILogInfoService logInfoService;

    @Resource
    private ValidatorUtil validatorUtil;

    @Resource
    private PlatformInfoCache platformInfoCache;

    @Resource
    private FileInfoCache fileInfoCache;

    @Resource
    private IndexHelper indexHelper;

    @Resource
    private FileInfoHelper fileInfoHelper;

    @Resource
    private DelFileThreadPool delFileThreadPool;

    @Resource
    private RegisterInfoCache registerInfoCache;

    @Resource
    private ConfigInfoCache configInfoCache;

    /**
     * 分页查询
     *
     * @param pageInfo pageInfo
     * @param fileInfo fileInfo
     * @return DataGrid
     */
    @Override
    public List<FileInfoDO> listInfoWithPage(PageInfo pageInfo, FileInfoDO fileInfo) {
        QueryWrapperBuilder<FileInfo> queryWrapperBuilder = this.buildQuery(fileInfo);
        queryWrapperBuilder.pageInfo(pageInfo).addSort(BaseEntity::getCreateTime, SortField.Type.LONG, true);
        List<FileInfo> list = this.indexHelper.searchByPage(queryWrapperBuilder.build());
        return BeanUtil.copyToList(list, FileInfoDO.class);
    }

    /**
     * 查询
     *
     * @param fileInfo fileInfo
     * @return List
     */
    @Override
    public List<FileInfoDO> listInfoWithoutPage(FileInfoDO fileInfo) {
        QueryWrapperBuilder<FileInfo> queryWrapperBuilder = this.buildQuery(fileInfo);
        queryWrapperBuilder.addSort(BaseEntity::getCreateTime, SortField.Type.LONG, true);
        List<FileInfo> list = this.indexHelper.search(queryWrapperBuilder.build());
        return BeanUtil.copyToList(list, FileInfoDO.class);
    }

    /**
     * 详情
     *
     * @param id        主键
     * @param checkFile 检查文件是否存在
     * @return FileInfo
     */
    @Override
    public FileInfo detail(String id, Boolean checkFile) {
        FileInfo fileInfo = this.fileInfoCache.get(id, false);
        if (Objects.nonNull(fileInfo)) {
            return fileInfo;
        }
        IdQueryWrapperBuilder<FileInfo> idQueryWrapperBuilder = new IdQueryWrapperBuilder<>(FileInfo.class, id);
        fileInfo = this.indexHelper.searchById(idQueryWrapperBuilder.build());
        if (Objects.isNull(fileInfo)) {
            throw new AppException(MessageEnum.PARAM_ERROR.getCode(), "文件不存在");
        }
        if (!Boolean.TRUE.equals(checkFile)) {
            return fileInfo;
        }
        File file = this.fileInfoHelper.getFile(fileInfo);
        if (!FileUtil.exist(file)) {
            throw new AppException(MessageEnum.PARAM_ERROR.getCode(), "文件不存在");
        }
        return fileInfo;
    }

    /**
     * 批量获取文件信息
     *
     * @param fileIds 文件id列表
     * @return list
     */
    @Override
    public List<FileInfo> detailFileByIds(List<String> fileIds) {
        if (CollUtil.isEmpty(fileIds)) {
            return CollUtil.newArrayList();
        }
        if (fileIds.size() > NumberConstant.ONE_HUNDRED) {
            throw new AppException(JdfSystemMsgEnum.LIST_FILE_INFO_LIMIT);
        }
        Set<String> set = CollUtil.newHashSet(fileIds);
        List<String> noCacheIdList = CollUtil.newArrayList();
        List<FileInfo> result = CollUtil.newArrayList();
        set.forEach(f -> {
            FileInfo fileInfo = this.fileInfoCache.get(f, false);
            if (null == fileInfo) {
                noCacheIdList.add(f);
                return;
            }
            result.add(fileInfo);
        });
        if (CollUtil.isNotEmpty(noCacheIdList)) {
            IdsQueryWrapperBuilder<FileInfo> idsQueryWrapperBuilder = new IdsQueryWrapperBuilder<>(FileInfo.class, noCacheIdList);
            List<FileInfo> sourceList = this.indexHelper.searchByIds(idsQueryWrapperBuilder.build());
            result.addAll(sourceList);
        }
        return result;
    }

    /**
     * 查找文件所在机器
     *
     * @param fileId 文件id
     * @return map：key=ip，value=文件是否有效
     */
    @Override
    public List<String> findIp(String fileId) {
        // 如果本机存在文件，则直接返回
        FileInfo fileInfo = this.detail(fileId, true);
        if (Objects.nonNull(fileInfo) && YesOrNoEnum.YES.getCode().equals(fileInfo.getValidFlag())) {
            String ip = this.configInfoCache.getValue(ConfigCodeEnum.BIND_IP.getCode());
            return CollUtil.newArrayList(ip);
        }
        // 获取当前工作空间所有机器列表
        String workspaceId = this.configInfoCache.getValue(ConfigCodeEnum.WORKSPACE_ID.getCode());
        Set<String> ips = this.registerInfoCache.getValidIpMap(workspaceId, false).get(workspaceId);
        return this.fileInfoHelper.parallelCheckFile(CollUtil.newArrayList(ips), fileId);
    }

    /**
     * 保存
     *
     * @param saveBO 文件信息
     */
    @Override
    public String saveInfo(FileInfoSaveBO saveBO) {
        // 校验platform是否有效
        PlatformInfo platformInfo = this.platformInfoCache.get(saveBO.getPlatformId(), false);
        if (null == platformInfo || YesOrNoEnum.NO.getCode().equals(platformInfo.getValidFlag())) {
            throw new AppException(JdfSystemMsgEnum.PLATFORM_CHECK_FAIL);
        }
        FileInfo fileInfo = new FileInfo();
        BeanUtil.copyProperties(saveBO, fileInfo);
        String workspaceId = this.configInfoCache.getValue(ConfigCodeEnum.WORKSPACE_ID.getCode());
        fileInfo.setWorkspaceId(workspaceId);
        this.indexHelper.addDocument(fileInfo);
        // 保存文件
        this.saveFile(fileInfo, saveBO.getByteArray());
        return fileInfo.getId();
    }

    /**
     * 保存文件到磁盘
     *
     * @param fileInfo 信息
     * @param b        文件流
     */
    @Override
    public void saveFile(FileInfo fileInfo, byte[] b) {
        String serverFileName = fileInfo.getId() + StringConstants.POINT + fileInfo.getFileExt();
        File file = FileUtil.file(fileInfo.getServerPath(), serverFileName);
        FileUtil.touch(file);
        FileUtil.writeBytes(b, file);
        // 保存日志
        this.logInfoService.saveInfo(fileInfo, LogOperateTypeEnum.ADD);
    }

    /**
     * 删除
     *
     * @param ids 主键列表
     * @return list
     */
    @Override
    public List<FileInfo> deleteInfo(List<String> ids) {
        if (CollUtil.isEmpty(ids)) {
            return CollUtil.newArrayList();
        }
        IdsQueryWrapperBuilder<FileInfo> idsQueryWrapperBuilder = new IdsQueryWrapperBuilder<>(FileInfo.class, ids);
        List<FileInfo> list = this.indexHelper.searchByIds(idsQueryWrapperBuilder.build());
        if (CollUtil.isEmpty(list)) {
            return CollUtil.newArrayList();
        }
        // 删除文件记录
        DeleteByIdsWrapperBuilder<FileInfo> deleteByIdsWrapperBuilder = new DeleteByIdsWrapperBuilder<>(FileInfo.class, ids);
        this.indexHelper.deleteByIds(deleteByIdsWrapperBuilder.build());
        // 保存日志
        list.forEach(f -> this.logInfoService.saveInfo(f, LogOperateTypeEnum.DEL));
        // 删除存储文件
        list.forEach(f -> this.delFileThreadPool.submit(f));
        return list;
    }

    /**
     * 变更文件状态
     *
     * @param platformId  平台ID
     * @param validList   变更为有效的文件id
     * @param inValidList 变更为无效的文件id
     */
    @Override
    public void trustFile(String platformId, List<String> validList, List<String> inValidList) {
        this.changeFlag(platformId, validList, YesOrNoEnum.YES);
        this.changeFlag(platformId, inValidList, YesOrNoEnum.NO);
    }

    /**
     * 转型
     *
     * @param file       MultipartFile
     * @param platformId 平台ID
     * @param scope      范围域
     * @return FileInfoSaveBO
     */
    @Override
    public FileInfoSaveBO toFileInfo(MultipartFile file, String platformId, Integer scope) throws IOException {
        String fileName = file.getOriginalFilename();
        if (CharSequenceUtil.isEmpty(fileName)) {
            throw new AppException(MessageEnum.PARAM_NULL.getCode(), "文件名不能为空");
        }
        String cacheFileName = CharSequenceUtil.subBefore(fileName, StringConstants.POINT, true);
        String extName = CharSequenceUtil.subAfter(fileName, StringConstants.POINT, true);
        Long fileSize = file.getSize();
        FileInfoSaveBO saveBO = new FileInfoSaveBO();
        saveBO.setPlatformId(platformId);
        saveBO.setFileName(cacheFileName);
        saveBO.setFileExt(extName);
        saveBO.setContentType(file.getContentType());
        saveBO.setFileSize(NumberUtil.div(fileSize, UNIT_M, NumberConstant.TWO));
        saveBO.setServerPath(this.createServerPath(null));
        saveBO.setValidFlag(YesOrNoEnum.NO.getCode());
        saveBO.setPageviewTimes(ZERO);
        saveBO.setByteArray(IoUtil.readBytes(file.getInputStream()));
        saveBO.setScope(scope);
        if (Objects.isNull(scope)) {
            saveBO.setScope(FileInfoScopeEnum.PUBLIC.getCode());
        }
        // 校验
        this.validatorUtil.fastValidate(saveBO);
        return saveBO;
    }

    /**
     * 创建文件路径
     *
     * @param date 日期YYYYMMDD
     * @return string
     */
    @Override
    public synchronized String createServerPath(String date) {
        String path = CharSequenceUtil.isEmpty(date) ? DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN) : date;
        String filePath = this.configInfoCache.getValue(ConfigCodeEnum.FILE_PATH.getCode());
        File file = FileUtil.file(filePath, path);
        if (!FileUtil.exist(file)) {
            FileUtil.mkdir(file);
        }
        return file.getAbsolutePath();
    }

    private void changeFlag(String platformId, List<String> idList, YesOrNoEnum yesOrNo) {
        Assert.notEmpty(platformId, "平台ID不能为空");
        if (CollUtil.isEmpty(idList)) {
            return;
        }
        IdsQueryWrapperBuilder<FileInfo> idsQueryWrapperBuilder = new IdsQueryWrapperBuilder<>(FileInfo.class, idList);
        List<FileInfo> list = this.indexHelper.searchByIds(idsQueryWrapperBuilder.build());
        if (CollUtil.isEmpty(list)) {
            return;
        }
        list.stream().filter(f -> platformId.equals(f.getPlatformId())).forEach(f -> {
            f.setValidFlag(yesOrNo.getCode());
            this.indexHelper.updateDocumentById(f);
        });
    }

    private QueryWrapperBuilder<FileInfo> buildQuery(FileInfoDO fileInfo) {
        QueryWrapperBuilder<FileInfo> builder = new QueryWrapperBuilder<>(FileInfo.class);
        if (CharSequenceUtil.isNotEmpty(fileInfo.getFileName())) {
            builder.matchStr(FileInfo::getFileName, fileInfo.getFileName(), MatchTypeEnum.ALL_LIKE, BooleanClause.Occur.MUST);
        }
        if (CharSequenceUtil.isNotEmpty(fileInfo.getPlatformId())) {
            builder.matchStr(FileInfo::getPlatformId, fileInfo.getPlatformId(), MatchTypeEnum.TERM, BooleanClause.Occur.MUST);
        }
        if (Objects.nonNull(fileInfo.getValidFlag())) {
            builder.matchInt(FileInfo::getValidFlag, fileInfo.getValidFlag(), BooleanClause.Occur.MUST);
        }
        if (Objects.nonNull(fileInfo.getCreateDateBegin())) {
            builder.matchRange(FileInfo::getCreateDate, fileInfo.getCreateDateBegin(), Integer.MAX_VALUE, BooleanClause.Occur.MUST);
        }
        if (Objects.nonNull(fileInfo.getCreateDateEnd())) {
            builder.matchRange(FileInfo::getCreateDate, Integer.MIN_VALUE, fileInfo.getCreateDateEnd(), BooleanClause.Occur.MUST);
        }
        return builder;
    }
}
